package com.xgimi.open.skill.datamanager.biz.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.xgimi.commons.util.StringUtil;
import com.xgimi.gmui.glog.sdk.GlogRecorder;
import com.xgimi.open.skill.datamanager.biz.common.Enum.ExceptionErrorCode;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizAssert;
import com.xgimi.open.skill.datamanager.biz.common.exception.BizException;
import com.xgimi.open.skill.datamanager.biz.common.redis.RedisLockService;
import com.xgimi.open.skill.datamanager.biz.constant.NluConstants;
import com.xgimi.open.skill.datamanager.biz.service.SkillVersionBizService;
import com.xgimi.open.skill.datamanager.biz.utils.RedisKeyBuilder;
import com.xgimi.open.skill.datamanager.domain.entity.*;
import com.xgimi.open.skill.datamanager.domain.service.*;
import com.xgimi.open.skill.datamanager.service.request.SkillVersionReqDTO;
import com.xgimi.open.skill.datamanager.service.response.SkillPublishRespDTO;
import com.xgimi.open.skill.datamanager.service.response.SkillVersionRespDTO;
import com.xgimi.open.skill.datamanager.service.response.page.IPageUtils;
import com.xgimi.open.skill.datamanager.service.response.page.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author coderzpw.zhang
 * @version 1.0
 * @description 描述
 * @date 2023/2/8 16:13
 */
@Slf4j
@Service
public class SkillVersionBizServiceImpl implements SkillVersionBizService {
    // 发布状态
    public static final Integer PUBLISHED_STATUS = 1;
    // 技能测试版本号 均为0
    public static final Long TEST_VERSION = 0L;
    // 线上版本最小的版本号
    public static final Long ONLINE_MIN_VERSION = 1L;

    private final RedisLockService redisLockService;
    private final SkillService skillService;
    private final SkillVersionService skillVersionService;
    private final WordStockService wordStockService;
    private final SlotService slotService;
    private final IntentService intentService;
    private final SpeechRuleService speechRuleService;
    private final IntentSlotService intentSlotService;
    private final EntryService entryService;
    private final GlogRecorder glogRecorder;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public SkillVersionBizServiceImpl(RedisLockService redisLockService,
                                      SkillService skillService,
                                      SkillVersionService skillVersionService,
                                      WordStockService wordStockService,
                                      SlotService slotService,
                                      IntentService intentService,
                                      SpeechRuleService speechRuleService,
                                      IntentSlotService intentSlotService,
                                      EntryService entryService,
                                      GlogRecorder glogRecorder) {
        this.redisLockService = redisLockService;
        this.skillService = skillService;
        this.skillVersionService = skillVersionService;
        this.wordStockService = wordStockService;
        this.slotService = slotService;
        this.intentService = intentService;
        this.speechRuleService = speechRuleService;
        this.intentSlotService = intentSlotService;
        this.entryService = entryService;
        this.glogRecorder = glogRecorder;
    }


    /**
     * 1、判断技能版本id是否为null？
     * 2、判断技能版本是否存在?
     * 3、判断当前版本是否最新的测试版本？
     *
     * @param skillVid
     * @return
     */
    @Override
    public SkillVersionDO checkSkillVersion(Long skillVid) {
        BizAssert.notNull(skillVid, ExceptionErrorCode.SKILL_VERSION_ID_IS_NULL);
        SkillVersionDO skillVersionDO = skillVersionService.querySkillVersionById(skillVid);
        BizAssert.notNull(skillVersionDO, ExceptionErrorCode.SKILL_VERSION_NOT_EXISTS);
        if (!TEST_VERSION.equals(skillVersionDO.getVersion())) {
            throw new BizException(ExceptionErrorCode.TEST_VERSION_NOT_NEWEST);
        }
        return skillVersionDO;
    }


    @Transactional
    @Override
    public SkillPublishRespDTO publishSkillVersion(SkillVersionReqDTO skillVersionReqDTO) {
        SkillPublishRespDTO ret = new SkillPublishRespDTO();
        SkillVersionDO skillVersionDO = new SkillVersionDO();
        BeanUtils.copyProperties(skillVersionReqDTO, skillVersionDO);
        Long skillId = skillVersionDO.getSkillId();
        SkillDO skillDO = skillService.queryById(skillId);
        BizAssert.notNull(skillDO, ExceptionErrorCode.SKILL_NOT_EXISTS);
        String skillLockKey = skillLock(skillDO.getBusinessId());
        try {
            ret = testDataSyncLine(skillVersionReqDTO, skillVersionDO, skillDO);
        } finally {
            redisLockService.unlock(skillLockKey);
        }
        return ret;
    }

    public SkillPublishRespDTO testDataSyncLine(SkillVersionReqDTO skillVersionReqDTO, SkillVersionDO skillVersionDO, SkillDO skillDO) {
        SkillPublishRespDTO ret = new SkillPublishRespDTO();
        // 把测试版本改成 发布版本 （测试版本一个技能只有一个）
        SkillVersionDO testVersion = skillVersionService.queryTestVersion(skillVersionDO);
        // 创建新的测试版本，从老的测试版本那里克隆
        // 技能的浅拷贝
        SkillVersionDO newTestVersion = new SkillVersionDO();
        BeanUtils.copyProperties(testVersion, newTestVersion);
        newTestVersion.setId(null);
        boolean isNewSuccess = skillVersionService.save(newTestVersion);
        // 技能深拷贝
        boolean isCopySuccess = copySkillVersion(testVersion, newTestVersion);
        if (isNewSuccess && isCopySuccess) {
            // 将老的测试版本的状态 修改为 发布状态，并保证版本自增
            List<SkillVersionDO> publishedVersions = skillVersionService.queryPublishedVersion(skillVersionDO);
            if (publishedVersions.size() == 0) {
                testVersion.setVersion(1L);
            } else {
                /* 发布版本递增 */
                testVersion.setVersion(publishedVersions.get(0).getVersion() + 1);
            }
            testVersion.setStatus(PUBLISHED_STATUS);
            testVersion.setPublishDesc(skillVersionReqDTO.getPublishDesc());
            testVersion.setPublishedBy(skillVersionReqDTO.getPublishedBy());
            testVersion.setPublishedAt(skillVersionReqDTO.getPublishedAt());
            boolean flag = skillVersionService.updateById(testVersion);
            ret.setPublished(flag);
            ret.setSkillVid(testVersion.getId());
            ret.setSkillName(skillDO.getSkillName());
            ret.setBusinessId(skillDO.getBusinessId());
            // 删除老的线上版本数据
            clearOldOnlineData(testVersion.getVersion(), testVersion.getSkillId());
        }
        if (ret.getPublished()) {
            skillDO.setPublished(PUBLISHED_STATUS);
            skillService.updateById(skillDO);
        }
        return ret;
    }

    /**
     * 清理上一个线上版本的所有数据
     */
    private void clearOldOnlineData(long skillVersion, long skillId) {
        // 当前版本是最低的线上版本
        if (ONLINE_MIN_VERSION.equals(skillVersion)) {
            return;
        }
        SkillVersionDO skillVersionDO = skillVersionService.queryBySkillIdAndVersion(skillId, skillVersion - 1);
        Long skillVid = skillVersionDO.getId();
        List<IntentDO> intentDOS = intentService.listBySkillVid(skillVid);
        List<Long> intentIds = intentDOS.stream().map(IntentDO::getId).collect(Collectors.toList());
        List<SlotDO> slotDOS = slotService.listBySkillVid(skillVid);
        List<Long> slotIds = slotDOS.stream().map(SlotDO::getId).collect(Collectors.toList());
        List<WordStockDO> wordStockDOS = wordStockService.listBySkillVid(skillVid);
        List<Long> wordStockIds = wordStockDOS.stream().map(WordStockDO::getId).collect(Collectors.toList());
        // 删除意图-槽位中间表的数据
        intentSlotService.physicalDeleteByIntentIds(intentIds);
        intentSlotService.physicalDeleteBySlotIds(slotIds);
        // 删除词库表的数据
        wordStockService.physicalDeleteBySkillVid(skillVid);
        // 删除槽位表的数据
        slotService.physicalDeleteBySkillVid(skillVid);
        // 删除意图表的数据
        intentService.physicalDeleteBySkillVid(skillVid);
        // 删除说法规则表的数据
        speechRuleService.physicalDeleteByIntentIds(intentIds);
        // 删除词条表的数据
        entryService.physicalDeleteByWordStockIds(wordStockIds);
    }


    /**
     * 技能的全量复制，包括 词库、槽位、意图、说法
     *
     * @param source
     * @param target
     */
    @Transactional
    public Boolean copySkillVersion(SkillVersionDO source, SkillVersionDO target) {
        Long sourceSkillVid = source.getId();
        Long targetSkillVId = target.getId();
        // 词库 新老id映射关系
        HashMap<Long, Long> wordStockIdMap = new HashMap<>();
        HashMap<Long, Long> slotIdMap = new HashMap<>();
        HashMap<Long, Long> intentIdMap = new HashMap<>();
        List<SlotDO> batchSlot = new ArrayList<>();
        // 复制词库
        copyWordStock(sourceSkillVid, targetSkillVId, wordStockIdMap);
        // 复制槽位
        copySlot(sourceSkillVid, targetSkillVId, batchSlot, wordStockIdMap, slotIdMap);
        // 复制意图
        copyIntent(sourceSkillVid, targetSkillVId, slotIdMap, intentIdMap);
        // 复制意图-槽位关联信息
        copyIntentSlot(sourceSkillVid, intentIdMap, slotIdMap);
        return Boolean.TRUE;
    }

    /**
     * 复制 意图-槽位
     */
    private void copyIntentSlot(Long sourceSkillVid, HashMap<Long, Long> intentIdMap, HashMap<Long, Long> slotIdMap) {
        List<SlotDO> slotDOS = slotService.listBySkillVid(sourceSkillVid);
        List<Long> slotIds = slotDOS.stream().map(SlotDO::getId).collect(Collectors.toList());
        List<IntentSlotDO> intentSlotDOS = intentSlotService.listBySlotIds(slotIds);
        List<IntentSlotDO> batchIntentSlot = new ArrayList<>();
        for (IntentSlotDO isd : intentSlotDOS) {
            IntentSlotDO intentSlot = new IntentSlotDO();
            BeanUtils.copyProperties(isd, intentSlot);
            intentSlot.setId(null);
            intentSlot.setIntentId(intentIdMap.get(isd.getIntentId()));
            intentSlot.setSlotId(slotIdMap.get(isd.getSlotId()));
            batchIntentSlot.add(intentSlot);
        }
        intentSlotService.saveBatch(batchIntentSlot);
    }


    /**
     * 复制词库
     */
    public void copyWordStock(Long sourceSkillVid, Long targetSkillVId, HashMap<Long, Long> wsIdMap) {
        List<WordStockDO> batchWordStock = new ArrayList<>();
        List<WordStockDO> wordStockDOs = wordStockService.listBySkillVid(sourceSkillVid);
        for (WordStockDO ws : wordStockDOs) {
            WordStockDO newWs = new WordStockDO();
            BeanUtils.copyProperties(ws, newWs);
            newWs.setId(null);
            newWs.setSkillVid(targetSkillVId);
            batchWordStock.add(newWs);
        }
        if (!CollectionUtils.isEmpty(batchWordStock)) {
            wordStockService.saveBatch(batchWordStock);
        }
        for (int i = 0; i < batchWordStock.size(); i++) {
            wsIdMap.put(wordStockDOs.get(i).getId(), batchWordStock.get(i).getId());
        }
        // 复制词条
        copyEntry(wordStockDOs, wsIdMap);
    }

    private void copyEntry(List<WordStockDO> wordStockDOs, HashMap<Long, Long> wsIdMap) {
        List<EntryDO> batchEntry = new ArrayList<>();
        List<Long> wordStockIds = wordStockDOs.stream().map(WordStockDO::getId).collect(Collectors.toList());
        List<EntryDO> entryDOS = entryService.queryByWordStockIds(wordStockIds);
        Map<Long, List<EntryDO>> collect = entryDOS.stream()
                .collect(Collectors.groupingBy(EntryDO::getWordStockId));
        collect.forEach((k, v) -> {
            for (EntryDO ed : v) {
                EntryDO newEd = new EntryDO();
                BeanUtils.copyProperties(ed, newEd);
                newEd.setId(null);
                newEd.setWordStockId(wsIdMap.get(k));
                batchEntry.add(newEd);
            }
        });
        if (!CollectionUtils.isEmpty(batchEntry)) {
            entryService.saveBatch(batchEntry);
        }
    }

    /**
     * 复制槽位
     */
    public void copySlot(Long sourceSkillVid, Long targetSkillVId, List<SlotDO> batchSlot, HashMap<Long, Long> wordStockIdMap, HashMap<Long, Long> slotIdMap) {
        List<SlotDO> slotDOs = slotService.listBySkillVid(sourceSkillVid);

        for (SlotDO sd : slotDOs) {
            SlotDO newSd = new SlotDO();
            newSd.setId(null);
            newSd.setSkillVid(targetSkillVId);
            newSd.setRelyonWordStock(wordStockIdMap.get(sd.getRelyonWordStock()));
            newSd.setSlotName(sd.getSlotName());
            batchSlot.add(newSd);
        }
        if (!CollectionUtils.isEmpty(batchSlot)) {
            slotService.saveBatch(batchSlot);
        }
        // 存储 老的slotId 与 新slotId的映射关系
        for (int i = 0; i < batchSlot.size(); i++) {
            slotIdMap.put(slotDOs.get(i).getId(), batchSlot.get(i).getId());
        }
    }

    /**
     * 复制意图
     */
    public void copyIntent(Long sourceSkillVid, Long targetSkillVId, HashMap<Long, Long> slotIdMap, HashMap<Long, Long> intentIdMap) {
        List<IntentDO> batchIntent = new ArrayList<>();
        List<IntentDO> intentDOs = intentService.listBySkillVid(sourceSkillVid);
        for (IntentDO itd : intentDOs) {
            IntentDO newItd = new IntentDO();
            BeanUtils.copyProperties(itd, newItd);
            newItd.setId(null);
            newItd.setSkillVid(targetSkillVId);
            batchIntent.add(newItd);
        }
        if (!CollectionUtils.isEmpty(batchIntent)) {
            intentService.saveBatch(batchIntent);
        }
        for (int i = 0; i < batchIntent.size(); i++) {
            intentIdMap.put(intentDOs.get(i).getId(), batchIntent.get(i).getId());
        }
        // 复制说法规则
        copySpeechRule(intentDOs, intentIdMap);
    }


    /**
     * 复制说法规则
     */
    public void copySpeechRule(List<IntentDO> intentDOs, HashMap<Long, Long> intentIdMap) {
        List<SpeechRuleDO> batchSpeechRule = new ArrayList<>();
        List<Long> intentIds = intentDOs.stream().map(IntentDO::getId).collect(Collectors.toList());
        List<SpeechRuleDO> speechRuleDOS = speechRuleService.queryByIntentIds(intentIds);
        Map<Long, List<SpeechRuleDO>> collect = speechRuleDOS.stream()
                .collect(Collectors.groupingBy(SpeechRuleDO::getIntentId));
        collect.forEach((k, v) -> {
            for (SpeechRuleDO srd : v) {
                SpeechRuleDO newSrd = new SpeechRuleDO();
                BeanUtils.copyProperties(srd, newSrd);
                newSrd.setId(null);
                newSrd.setIntentId(intentIdMap.get(k));
                batchSpeechRule.add(newSrd);
            }
        });
        if (!CollectionUtils.isEmpty(batchSpeechRule)) {
            speechRuleService.saveBatch(batchSpeechRule);
        }
    }

    private List<Long> getRelationIntentId(String relationIntent) {
        List<Long> intentIds = new ArrayList<>();
        if (StringUtil.isEmpty(relationIntent)) {
            return intentIds;
        }

        String[] split = relationIntent.split(",");
        for (String intentIdStr : split) {
            long intentId = Long.parseLong(intentIdStr);
            intentIds.add(intentId);
        }

        return intentIds;
    }


    @Override
    public PageResult<SkillVersionRespDTO> publishedList(SkillVersionReqDTO skillVersionReqDTO) {
        Long skillId = skillVersionReqDTO.getSkillId();
        BizAssert.notNull(skillId, ExceptionErrorCode.SKILL_ID_IS_NULL);
        SkillDO skillDO = skillService.queryById(skillId);
        BizAssert.notNull(skillDO, ExceptionErrorCode.SKILL_NOT_EXISTS);
        // 构造分页查询的入参
        IPage<SkillVersionDO> page = new Page<>(skillVersionReqDTO.getCurrentPage(), skillVersionReqDTO.getPageSize());
        // 调用domain层分页查询的方法
        IPage<SkillVersionDO> iPage = skillVersionService.pageQuery(page, skillId);
        // 若查询没结果，则返回空pageResult
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
            log.info("no such skill!");
            return new PageResult<>(Lists.newArrayList(), IPageUtils.noDataPaginator(skillVersionReqDTO.getPageSize()));
        }
        List<SkillVersionRespDTO> list = iPage.getRecords().stream().map(d -> {
            SkillVersionRespDTO dto = new SkillVersionRespDTO();
            BeanUtils.copyProperties(d, dto);
            return dto;
        }).collect(Collectors.toList());
        return new PageResult<>(list, IPageUtils.transformToPaginator(iPage));
    }

    @Override
    public Boolean deleteSkillVersion(SkillVersionReqDTO skillVersionReqDTO) {
        BizAssert.notNull(skillVersionReqDTO.getId(), ExceptionErrorCode.SKILL_VERSION_ID_IS_NULL);
        SkillVersionDO skillVersionDO = new SkillVersionDO();
        BeanUtils.copyProperties(skillVersionReqDTO, skillVersionDO);
        // 先根据技能id查询 是否存在该技能
        SkillVersionDO res = skillVersionService.querySkillVersionById(skillVersionDO.getId());
        BizAssert.notNull(res, ExceptionErrorCode.SKILL_NOT_EXISTS);
        // 删除技能
        boolean ret = skillVersionService.removeById(skillVersionDO.getId());
        // 级联删除
        skillVersionCascadingDelete(skillVersionReqDTO);

        // 记录glog
        glogRecorder.recordDel(skillVersionReqDTO.getUpdatedBy(),
                "skillVersion", "deleteSkillVersion",
                skillVersionReqDTO.toString());
        return ret;
    }

    private void skillVersionCascadingDelete(SkillVersionReqDTO skillVersionReqDTO) {
        List<Long> skillVids = new ArrayList<>();
        skillVids.add(skillVersionReqDTO.getId());
        // 查询技能版本下的所有意图
        List<IntentDO> intentDOS = intentService.listBySkillVids(skillVids);
        List<Long> intentIds = intentDOS.stream().map(IntentDO::getId).collect(Collectors.toList());
        try {
            // 批量删除词库、槽位、意图、说法
            wordStockService.removeBySkillVids(skillVids);
            slotService.removeBySkillVids(skillVids);
            intentService.removeBySkillVids(skillVids);
            speechRuleService.removeByIntentIds(intentIds);
        } catch (Exception e) {
            log.error("级联删除失败：{}", e);
        }
    }


    @Override
    public SkillVersionRespDTO queryById(SkillVersionReqDTO skillVersionReqDTO) {
        SkillVersionDO skillVersionDO = skillVersionService.getById(skillVersionReqDTO.getId());
        SkillVersionRespDTO skillVersionRespDTO = new SkillVersionRespDTO();
        BeanUtils.copyProperties(skillVersionDO, skillVersionRespDTO);
        return skillVersionRespDTO;
    }

    @Override
    public SkillVersionRespDTO getTestSkillVersion(Long skillId) {
        SkillVersionRespDTO ret = new SkillVersionRespDTO();
        SkillVersionDO testSkillVersion = skillVersionService.getTestSkillVersion(skillId);
        BeanUtils.copyProperties(testSkillVersion, ret);
        return ret;
    }

    @Override
    public SkillVersionRespDTO getPublishedMaxVersion(Long skillId) {
        SkillVersionRespDTO ret = new SkillVersionRespDTO();
        SkillVersionDO publishedMaxVersion = skillVersionService.getPublishedMaxVersion(skillId);
        BeanUtils.copyProperties(publishedMaxVersion, ret);
        return ret;
    }

    @Override
    public SkillVersionRespDTO getTestSkillVersion(String skillBusinessId) {
        SkillVersionRespDTO ret = new SkillVersionRespDTO();
        SkillDO skillDO = skillService.queryByBusinessId(skillBusinessId);
        SkillVersionDO testSkillVersion = skillVersionService.getTestSkillVersion(skillDO.getId());
        BeanUtils.copyProperties(testSkillVersion, ret);
        return ret;
    }

    @Override
    public SkillVersionRespDTO getPublishedMaxVersion(String skillBusinessId) {
        SkillVersionRespDTO ret = new SkillVersionRespDTO();
        SkillDO skillDO = skillService.queryByBusinessId(skillBusinessId);
        SkillVersionDO publishedMaxVersion = skillVersionService.getPublishedMaxVersion(skillDO.getId());
        BeanUtils.copyProperties(publishedMaxVersion, ret);
        return ret;
    }

    private String setToStr(Set<Long> set) {
        StringBuilder str = new StringBuilder();
        for (Long s : set) {
            if (StringUtil.isBlank(s.toString()))
                continue;
            str.append(s).append(",");
        }
        String setStr = str.toString();
        return setStr.substring(0, setStr.length() - 1);
    }

    /**
     * 加技能锁
     *
     * @param businessId
     */
    private String skillLock(String businessId) {
        String skillUpdateLockKey = RedisKeyBuilder.buildSkillUpdateLockKey(businessId);
        boolean locked = redisLockService.lock(skillUpdateLockKey, NluConstants.NLU_LOCK_EXPIRE_TTS,
                NluConstants.NLU_LOCK_WAIT_TIME_MILLS);
        if (!locked) {
            log.warn("获取技能写锁失败 SkillAppId:{}", businessId);
            throw new BizException(ExceptionErrorCode.SKILL_UPDATE_TRAIN);
        }
        return skillUpdateLockKey;
    }
}
